/*
 * Copyright 2011 SWM Services GmbH.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package de.swm.commons.mobile.client.widgets;

import java.util.Date;

import com.google.gwt.dom.client.InputElement;
import com.google.gwt.dom.client.Style.FontWeight;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.TextBox;

import de.swm.commons.mobile.client.SWMMobile;
import de.swm.commons.mobile.client.widgets.DateTextBox.DateStyle;



/**
 * Popup to select date and time.
 */
public class DatePopup extends PopupPanel {

	/** Additional style: margin. */
	private static final double MARGIN_YEAR = 12.0;

	/** Additional style: margin. */
	private static final double MARGIN_PIXEL_SIZE_4_0 = 4.0;

	/** Number Formatter for two-digit number strings. */
	private NumberFormat numFormat = NumberFormat.getFormat("00");

	/**
	 * Date selection handler.
	 */
	public interface DateSelectionHandler {

		/**
		 * Sets the selected date.
		 * 
		 * @param selectedDate
		 *            the selected date
		 */
		void dateSelected(Date selectedDate);



		/**
		 * Called, if date selection was canceled.
		 */
		void dateSelectionCancelled();

	}

	/**
	 * Defines the different fields in the popup.
	 */
	private enum DateField {
		DAY, MONTH, YEAR, HOUR, MINUTE
	}

	/** The underlying selection handler. */
	private DateSelectionHandler selectionHandler;

	/** Label displaying the day. */
	private TextBox dayLabel;

	/** Label displaying the month. */
	private TextBox monthLabel;

	/** Label displaying the year. */
	private TextBox yearLabel;

	/** Label displaying the hour. */
	private TextBox hourLabel;

	/** Label displaying the minute. */
	private TextBox minuteLabel;

	/** Delegate for date calculations. */
	private DateCalculation dateCalc;



	/**
	 * Default constructor.
	 * 
	 * @param givenDate
	 *            date to start with
	 * @param dateStyle
	 *            der date style
	 * @param handler
	 *            date selection handler
	 */
	public DatePopup(Date givenDate, final DateStyle dateStyle, DateSelectionHandler handler) {

		setStyleName(SWMMobile.getTheme().getMGWTCssBundle().getPopupsCss().datePopup());
		setGlassStyleName(SWMMobile.getTheme().getMGWTCssBundle().getPopupsCss().dateGlassPanel());

		selectionHandler = handler;
		if (givenDate == null) {
			givenDate = new Date();
		}

		dateCalc = new DateCalculation(givenDate);

		if (SWMMobile.getOsDetection().isIOs() && SWMMobile.getOsDetection().isIOS5()) {
			renderIOS5DateBox(givenDate, dateStyle);
		} else {
			renderDateBox();
		}
	}



	/**
	 * Renders the date box for devices where native date input field is not avalilable.
	 * 
	 * @param givenDate
	 * @param dateStyle
	 */
	private void renderIOS5DateBox(Date givenDate, DateStyle dateStyle) {
		VerticalPanel mainPanel = new VerticalPanel();

		final InputElement dateInput;
		if (dateStyle.equals(DateStyle.DATE)) {
			dateInput = createNumberInputElement("date");
		} else {
			// datetime/datetime-local
			dateInput = createNumberInputElement("datetime-local");
		}

		dateInput.focus();
		final TextBox dateTextBox = TextBox.wrap(dateInput);
		dateTextBox.setSize("95%", "20px");
		dateTextBox.setValue(dateCalc.formatToRfc3339Timestamp(givenDate, true));

		mainPanel.add(dateTextBox);

		HorizontalPanel commandPanel = new HorizontalPanel();
		commandPanel.addStyleName(SWMMobile.getTheme().getMGWTCssBundle().getPopupsCss().dateCommandPanel());
		Button okButton = new Button(SWMMobile.getI18N().confirmButton(), new ClickHandler() {

			/**
			 * {@inheritDoc}
			 */
			@Override
			public void onClick(ClickEvent event) {
				hide();
				selectionHandler.dateSelected(dateCalc.parseRfc3339Timestamp(dateTextBox.getText().trim(), true));
			}

		});
		okButton.addStyleName(SWMMobile.getTheme().getMGWTCssBundle().getPopupsCss().dateOkButton());
		commandPanel.add(okButton);
		Button cancelButton = new Button(SWMMobile.getI18N().cancelButton(), new ClickHandler() {

			/**
			 * {@inheritDoc}
			 */
			@Override
			public void onClick(ClickEvent event) {
				hide();
				selectionHandler.dateSelectionCancelled();
			}

		});
		cancelButton.addStyleName(SWMMobile.getTheme().getMGWTCssBundle().getPopupsCss().dateCancelButton());
		commandPanel.add(cancelButton);
		mainPanel.add(commandPanel);

		setWidget(mainPanel);
		dateTextBox.setFocus(true);
	}



	/**
	 * Renders the date box for devices where native date input field is not avalilable.
	 */
	private void renderDateBox() {
		VerticalPanel mainPanel = new VerticalPanel();

		HorizontalPanel spinnerPanel = new HorizontalPanel();
		VerticalPanel dayPanel = createSpinner(DateField.DAY);
		dayPanel.getElement().getStyle().setMarginRight(MARGIN_PIXEL_SIZE_4_0, Unit.PX);
		spinnerPanel.add(dayPanel);
		VerticalPanel monthPanel = createSpinner(DateField.MONTH);
		monthPanel.getElement().getStyle().setMarginRight(MARGIN_PIXEL_SIZE_4_0, Unit.PX);
		spinnerPanel.add(monthPanel);
		VerticalPanel yearPanel = createSpinner(DateField.YEAR);
		yearPanel.getElement().getStyle().setMarginRight(MARGIN_YEAR, Unit.PX);
		spinnerPanel.add(yearPanel);
		spinnerPanel.add(createSpinner(DateField.HOUR));
		Label separatorLabel = new Label(":");
		separatorLabel.getElement().getStyle().setFontWeight(FontWeight.BOLD);
		spinnerPanel.add(separatorLabel);
		spinnerPanel.add(createSpinner(DateField.MINUTE));
		mainPanel.add(spinnerPanel);

		HorizontalPanel commandPanel = new HorizontalPanel();
		commandPanel.addStyleName(SWMMobile.getTheme().getMGWTCssBundle().getPopupsCss().dateCommandPanel());
		Button okButton = new Button("Ok", new ClickHandler() {

			/**
			 * {@inheritDoc}
			 */
			@Override
			public void onClick(ClickEvent event) {
				hide();
				Date selectedDate = dateCalc.getDate();
				selectionHandler.dateSelected(selectedDate);
			}

		});
		okButton.addStyleName(SWMMobile.getTheme().getMGWTCssBundle().getPopupsCss().dateOkButton());
		commandPanel.add(okButton);
		Button cancelButton = new Button("Cancel", new ClickHandler() {

			/**
			 * {@inheritDoc}
			 */
			@Override
			public void onClick(ClickEvent event) {
				hide();
				selectionHandler.dateSelectionCancelled();
			}

		});
		cancelButton.addStyleName(SWMMobile.getTheme().getMGWTCssBundle().getPopupsCss().dateCancelButton());
		commandPanel.add(cancelButton);
		mainPanel.add(commandPanel);

		setWidget(mainPanel);
	}



	/**
	 * Will display the popup centered.
	 * 
	 * @param glassEffect
	 *            true if glass efect
	 */
	public void showCentered(boolean glassEffect) {
		setGlassEnabled(glassEffect);
		setPopupPositionAndShow(new PositionCallback() {

			/**
			 * {@inheritDoc}
			 */
			@Override
			public void setPosition(int offsetWidth, int offsetHeight) {
				int left = (Window.getClientWidth() - offsetWidth) / 2;
				int top = (Window.getClientHeight() - offsetHeight) / 2;
				setPopupPosition(left, top);
			}
		});
	}



	/**
	 * Will create the spinner buttons.
	 * 
	 * @param df
	 *            the date field
	 * @return spinner button panel
	 */
	private VerticalPanel createSpinner(final DateField df) {
		VerticalPanel vPanel = new VerticalPanel();
		ImageButton upButton = new ImageButton(SWMMobile.getTheme().getMGWTImageBundle().arrowup(), new ClickHandler() {

			/**
			 * {@inheritDoc}
			 */
			@Override
			public void onClick(ClickEvent event) {
				switch (df) {
					case DAY:
						dateCalc.incrementDay();
						break;
					case MONTH:
						dateCalc.incrementMonth();
						break;
					case YEAR:
						dateCalc.incrementYear();
						break;
					case HOUR:
						dateCalc.incrementHour();
						break;
					case MINUTE:
						dateCalc.incrementMinute();
						break;
					default:
						break;
				}
				updateLabels();
			}

		});
		vPanel.add(upButton);

		final TextBox label;
		switch (df) {
			case DAY:
				dayLabel = new TextBox();
				dayLabel.setWidth("30px");
				dayLabel.setText(dateCalc.getDay() + ".");
				label = dayLabel;
				break;
			case MONTH:
				monthLabel = new TextBox();
				monthLabel.setWidth("30px");
				monthLabel.setText(dateCalc.getMonth() + ".");
				label = monthLabel;
				break;
			case YEAR:
				yearLabel = new TextBox();
				yearLabel.setWidth("50px");
				yearLabel.setText(dateCalc.getYear());
				label = yearLabel;
				break;
			case HOUR:
				hourLabel = new TextBox();
				hourLabel.setWidth("30px");
				hourLabel.setText(numFormat.format(dateCalc.getHour()));
				label = hourLabel;
				break;
			case MINUTE:
				minuteLabel = new TextBox();
				minuteLabel.setWidth("30px");
				label = minuteLabel;
				minuteLabel.setText(numFormat.format(dateCalc.getMinute()));
				break;
			default:
				label = new TextBox();
				break;
		}
		label.addFocusHandler(new FocusHandler() {

			@Override
			public void onFocus(FocusEvent event) {
				label.setText("");
			}
		});
		label.addBlurHandler(new BlurHandler() {

			@Override
			public void onBlur(BlurEvent event) {
				switch (df) {
					case DAY:
						dateCalc.setDay(label.getText());
						updateLabels();
						break;
					case MONTH:
						dateCalc.setMonth(label.getText());
						updateLabels();
						break;
					case YEAR:
						dateCalc.setYear(label.getText());
						updateLabels();
						break;
					case HOUR:
						dateCalc.setHour(label.getText());
						updateLabels();
						break;
					case MINUTE:
						dateCalc.setMinute(label.getText());
						updateLabels();
						break;
					default:
						break;
				}

			}
		});

		label.setStyleName(SWMMobile.getTheme().getMGWTCssBundle().getPopupsCss().dateValueLabel());
		vPanel.add(label);

		ImageButton downButton = new ImageButton(SWMMobile.getTheme().getMGWTImageBundle().arrowdown(),
				new ClickHandler() {

					@Override
					public void onClick(ClickEvent event) {
						switch (df) {
							case DAY:
								dateCalc.decrementDay();
								break;
							case MONTH:
								dateCalc.decrementMonth();
								break;
							case YEAR:
								dateCalc.decrementYear();
								break;
							case HOUR:
								dateCalc.decrementHour();
								break;
							case MINUTE:
								dateCalc.decrementMinute();
								break;
							default:
								break;
						}
						updateLabels();
					}
				});
		vPanel.add(downButton);

		return vPanel;
	}



	/**
	 * Update the labels with internal state.
	 */
	private void updateLabels() {
		yearLabel.setText(dateCalc.getYear());
		monthLabel.setText(dateCalc.getMonth() + ".");
		dayLabel.setText(dateCalc.getDay() + ".");
		hourLabel.setText(numFormat.format(dateCalc.getHour()));
		minuteLabel.setText(numFormat.format(dateCalc.getMinute()));
	}



	/**
	 * Create the HTML input element.
	 * 
	 * @param type
	 *            the type
	 * @return the input element.
	 */
	private static native InputElement createNumberInputElement(String type) /*-{
																				var e = $doc.createElement("INPUT");
																				e.type = type;
																				return e;
																				}-*/;

}
